package org.example.elasticsearch;


import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.Refresh;
import co.elastic.clients.elasticsearch._types.mapping.Property;
import co.elastic.clients.elasticsearch.core.*;
import co.elastic.clients.elasticsearch.core.bulk.BulkOperation;
import co.elastic.clients.elasticsearch.core.bulk.UpdateAction;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.elasticsearch.core.search.HitsMetadata;
import co.elastic.clients.elasticsearch.indices.CreateIndexResponse;
import co.elastic.clients.elasticsearch.indices.DeleteIndexResponse;
import co.elastic.clients.elasticsearch.indices.ElasticsearchIndicesClient;
import co.elastic.clients.transport.endpoints.BooleanResponse;
import com.alibaba.fastjson2.JSONObject;
import org.example.domain.PageResult;
import org.example.elasticsearch.config.ESRestClientBuilder;
import org.example.elasticsearch.domain.ESDoc;
import org.example.elasticsearch.query.ESQueryBuilder;
import org.example.utils.Func;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchProperties;

import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.*;
import java.util.function.Function;

/**
 * @author: sql
 * @date: 2024/6/7
 * @Description: 高级客户端操作工具
 * @version: 1.0
 */
@Getter
@Slf4j
public class ESService {

    private final ElasticsearchProperties properties;
    private final ElasticsearchClient client;
    private final ElasticsearchIndicesClient indices;

    public ESService(ElasticsearchClient client, ElasticsearchProperties properties) {
        this.properties = properties;
        this.client = client;
        this.indices = client.indices();
    }

    /**
     * 创建索引
     *
     * @param indexName 索引
     * @return 是否
     */
    public boolean createIndex(String indexName) {
        if (isIndexExist(indexName)) {
            return false;
        }
        try {
            // 创建索引
            CreateIndexResponse createIndexResponse = indices.create(c -> c.index(indexName));
            // 响应状态
            boolean acknowledged = createIndexResponse.acknowledged();
            log.info("执行建立成功？{}", acknowledged);
            return acknowledged;
        } catch (IOException e) {
            log.error("IOException", e);
        }
        return false;
    }

    /**
     * 删除索引
     *
     * @param indexName 索引
     * @return 是否
     */
    public boolean deleteIndex(String indexName) {
        if (!isIndexExist(indexName)) {
            return false;
        }
        try {
            DeleteIndexResponse deleteIndexResponse = client.indices().delete(e -> e.index(indexName));
            boolean acknowledged = deleteIndexResponse.acknowledged();
            if (acknowledged) {
                log.info("delete index {}  successfully!", indexName);
            } else {
                log.error("Fail to delete index {}", indexName);
            }
            return acknowledged;
        } catch (IOException e) {
            log.error("IOException", e);
        }
        return false;
    }

    /**
     * 判断索引是否存在
     *
     * @param indexName 索引
     * @return 是否
     */
    public boolean isIndexExist(String indexName) {
        try {
            BooleanResponse response = indices.exists(e -> e.index(indexName));
            boolean exists = response.value();
            if (exists) {
                log.info("Index [{}] is exist!", indexName);
            } else {
                log.error("Index [{}] is not exist!", indexName);
            }
            return exists;
        } catch (IOException e) {
            log.error("IOException", e);
        }
        return false;
    }

    /**
     * 创建 ES Mapping
     *
     * @param esIndex
     */
    public void updMapping(ESIndex esIndex) {
        String indexName = ESUtil.getIndexName(esIndex);
        Class<?> esDocClass = esIndex.getEsDocClass();
        try {
            createIndex(indexName);
            indices.putMapping(x -> x.index(indexName).properties(getPropertyMap(esDocClass)));
        } catch (Exception e) {
            log.error("IOException", e);
        }
    }

    /**
     * java 类型转 es 类型
     *
     * @param clzss
     * @return
     */
    public Map<String, Property> getPropertyMap(Class<?> clzss) {
        Map<String, Property> map = new HashMap<>();
        List<Field> fieldList = Func.getFieldList(clzss);
        for (Field field : fieldList) {
            String name = field.getName();
            Property property = switch (field.getType().getSimpleName()) {
                case "String" -> Property.of(x -> x.keyword(v -> v));
                case "Integer" -> Property.of(x -> x.integer(v -> v));
                case "BigDecimal" -> Property.of(x -> x.float_(v -> v));
                case "Date" -> Property.of(x -> x.date(v -> v.format("strict_date_optional_time||epoch_millis")));
                default -> null;
            };
            map.put(name, property);
        }
        return map;
    }

    /**
     * 刷新索引
     *
     * @param esIndex
     */
    public void refresh(ESIndex esIndex) {
        String index = ESUtil.getIndexName(esIndex);
        try {
            indices.refresh(x -> x.index(index));
        } catch (Exception e) {
            log.error("refresh", e);
        }
    }


    // 文档

    /**
     * 创建文档
     *
     * @param esIndex 索引，类似数据库
     * @param target  要增加的数据
     */
    public void create(ESIndex esIndex, Object target) {
        String index = ESUtil.getIndexName(esIndex);
        ESDoc doc = ESUtil.convertToEsDoc(esIndex, target);
        String docId = ESUtil.getDocId(esIndex, target);
        try {
            CreateRequest<Object> request = CreateRequest.of(e -> e
                    .index(index)
                    .id(docId)
                    .document(doc)
                    .refresh(Refresh.True));
            CreateResponse createResponse = client.create(request);
            log.info("创建文档：{}", createResponse.result());
        } catch (Exception e) {
            log.error("IOException", e);
        }
    }

    /**
     * 修改文档
     *
     * @param esIndex 索引
     * @param target  文档
     */
    public void update(ESIndex esIndex, Object target) {
        String indexName = ESUtil.getIndexName(esIndex);
        ESDoc doc = ESUtil.convertToEsDoc(esIndex, target);
        String docId = ESUtil.getDocId(esIndex, doc);
        if (!existDoc(indexName, docId)) {
            create(esIndex, doc);
            return;
        }
        try {
            UpdateResponse<?> response = client.update(e -> e.index(indexName).id(docId).doc(doc).refresh(Refresh.True), doc.getClass());
            log.info("updateResponse.result() = {}", response.result());
            if ("OK".equals(response.result().toString())) {
                log.info("Doc Update Successful. Version：{}", response);
            }
        } catch (IOException e) {
            log.error("IOException", e);
        }
    }

    /**
     * 文档是否存在
     *
     * @param index 索引
     * @param id    ID
     * @return 是否
     */
    public boolean existDoc(String index, Serializable id) {
        try {
            BooleanResponse response = client.exists(e -> e.index(index).id(id.toString()));
            return response.value();
        } catch (IOException e) {
            log.error("IOException", e);
        }
        return false;
    }

    /**
     * 删除文档
     *
     * @param indexName 索引
     * @param id        ID
     * @return 错误提示
     */
    public String del(String indexName, String id) {
        try {
            DeleteResponse deleteResponse = client.delete(e -> e.index(indexName).id(id).refresh(Refresh.True));
            return deleteResponse.result().toString();
        } catch (IOException e) {
            log.error("IOException", e);
        }
        return "ERROR";
    }

    /**
     * 删除文档
     *
     * @param esIndex 索引
     * @param id      ID
     * @return 错误提示
     */
    public String del(ESIndex esIndex, String id) {
        String indexName = ESUtil.getIndexName(esIndex);
        return del(indexName, id);
    }

    /**
     * 批量添加文档
     *
     * @param esIndex 索引
     * @param docs  文档
     * @return 是否
     */
    public boolean bulkCreateDoc(ESIndex esIndex, Collection<?> docs) {
        List<BulkOperation> dataList = new ArrayList<>();
        String indexName = ESUtil.getIndexName(esIndex);
        for (Object target : docs) {
            ESDoc doc = ESUtil.convertToEsDoc(esIndex, target);
            String docId = ESUtil.getDocId(esIndex, doc);
            BulkOperation op = new BulkOperation.Builder()
                    .create(d -> d.document(doc).id(docId).index(indexName))
                    .build();
            dataList.add(op);
        }
        try {
            // 调用bulk方法执行批量插入操作
            BulkResponse bulkResponse = client.bulk(e -> e.index(indexName).operations(dataList).refresh(Refresh.True));
            System.out.println("bulkResponse.items() = " + bulkResponse.items());
            return false;
        } catch (IOException e) {
            log.error("IOException：", e);
        }
        return false;
    }

    public boolean bulkUpdateDoc(ESIndex esIndex, Collection<?> docs) {
        List<BulkOperation> dataList = new ArrayList<>();
        String indexName = ESUtil.getIndexName(esIndex);
        for (Object target : docs) {
            ESDoc doc = ESUtil.convertToEsDoc(esIndex, target);
            String docId = ESUtil.getDocId(esIndex, doc);
            BulkOperation op = new BulkOperation.Builder()
                    .update(i -> i.action(new UpdateAction.Builder<>().doc(doc).docAsUpsert(true).build()).id(docId).index(indexName))
                    .build();
            dataList.add(op);
        }
        try {
            // 调用bulk方法执行批量插入操作
            BulkResponse bulkResponse = client.bulk(e -> e.index(indexName).operations(dataList).refresh(Refresh.True));
            System.out.println("bulkResponse.items() = " + bulkResponse.items());
            return false;
        } catch (IOException e) {
            log.error("IOException：", e);
        }
        return false;
    }

    public boolean bulkDeleteDoc(ESIndex esIndex, Collection<?> docs) {
        List<BulkOperation> dataList = new ArrayList<>();
        String indexName = ESUtil.getIndexName(esIndex);
        for (Object target : docs) {
            ESDoc doc = ESUtil.convertToEsDoc(esIndex, target);
            String docId = ESUtil.getDocId(esIndex, doc);
            BulkOperation op = new BulkOperation.Builder()
                    .delete(i -> i.index(indexName).id(docId))
                    .build();
            dataList.add(op);
        }
        try {
            // 调用bulk方法执行批量插入操作
            BulkResponse bulkResponse = client.bulk(e -> e.index(indexName).operations(dataList).refresh(Refresh.True));
            System.out.println("bulkResponse.items() = " + bulkResponse.items());
            return false;
        } catch (IOException e) {
            log.error("IOException：", e);
        }
        return false;
    }
    //
    ///**
    // * 全量查询文档
    // *
    // * @param index 索引
    // * @param clazz 返回类型
    // * @return res
    // */
    //public T queryAll(String index, Class<T> clazz) {
    //    try {
    //        // 全量查询
    //        SearchResponse<T> searchResponse = client.search(e -> e.index(index).query(q -> q.matchAll(m -> m)), clazz);
    //        HitsMetadata<T> hits = searchResponse.hits();
    //        for (Hit<T> hit : hits.hits()) {
    //            System.out.println("user = " + hit.source().toString());
    //        }
    //        System.out.println("searchResponse.hits().total().value() = " + searchResponse.hits().total().value());
    //
    //        return null;
    //    } catch (IOException e) {
    //        log.error("IOException：", e);
    //    }
    //    return null;
    //}

    public void delBuilderQuery(ESQueryBuilder builder) {
        builder.sourceClass(ESDoc.class);
        SearchResponse<JSONObject> response = this.defaultSearch(builder, JSONObject.class);
        HitsMetadata<JSONObject> hits = response.hits();
        for (Hit<JSONObject> hit : hits.hits()) {
            del(builder.getEsIndex(), hit.id());
        }
    }

    public <T, R> PageResult<T> pageSearch(ESQueryBuilder builder, Class<R> docClazz, Function<Hit<R>, T> fn) {
        PageResult<T> pageResult = new PageResult<>();
        SearchResponse<R> response = this.defaultSearch(builder, docClazz);
        if (Objects.isNull(response)) {
            return pageResult;
        }
        HitsMetadata<R> hits = response.hits();
        if (Objects.isNull(hits)) {
            return pageResult;
        }
        List<T> dataList = new ArrayList<>();
        for (Hit<R> hit : hits.hits()) {
            T apply = fn.apply(hit);
            dataList.add(apply);
        }
        pageResult.setArray(dataList);
        if (response.hits().total() != null) {
            pageResult.setTotal(response.hits().total().value());
        }
        //Map<String, Aggregate> aggregations = response.aggregations();
        //System.out.println(aggregations);
        //System.out.println("searchResponse.hits().total().value() = " + response.hits().total().value());
        return pageResult;
    }

    /**
     * 默认查询
     * 主要解决字段不匹配问题
     *
     * @param builder
     * @return
     */
    public <R> SearchResponse<R> defaultSearch(ESQueryBuilder builder, Class<R> docClazz) {
        SearchRequest request = builder.build();
        ElasticsearchClient clientNew = ESRestClientBuilder.createClient(properties);
        try {
            return clientNew.search(request, docClazz);
        } catch (IOException e) {
            log.error("IOException：", e);
        }
        return null;
    }

    //
    ///**
    // * 设置高亮字段
    // *
    // * @param fields 字段
    // * @return 高亮构建器
    // */
    //public void highlightBuilder(String... fields) {
    //
    //    SearchRequest request = SearchRequest.of(s -> s.index("user_test").query(q -> q.term(t -> t.field("name").value("test3"))));
    //
    //    search(s.index("user_test")
    //                    .query(q -> q.term(t -> t.field("name").value("test3")))
    //                    .highlight(h -> h.fields("name", f -> f.preTags("<font color='red'>").postTags("</font>")))
    //            , User.class)
    //
    //    SearchResponse<User> searchResponse = client.search(s ->
    //                    s.index("user_test")
    //                            .query(q -> q.term(t -> t.field("name").value("test3")))
    //                            .highlight(h -> h.fields("name", f -> f.preTags("<font color='red'>").postTags("</font>")))
    //            , User.class);
    //    searchResponse.hits().hits().forEach(h -> System.out.println(h.source().toString()));
    //
    //    Highlight highlight = new Highlight();
    //
    //    Highlight.of().Builder
    //
    //    // 高亮条件
    //    HighlightBuilder highlightBuilder = new HighlightBuilder(); // 生成高亮查询器
    //    for (String field : fields) {
    //        highlightBuilder.field(field); // 高亮查询字段
    //    }
    //    highlightBuilder.requireFieldMatch(false); // 如果要多个字段高亮,这项要为false
    //    highlightBuilder.preTags("<span style=\"color:red\">"); // 高亮设置
    //    highlightBuilder.postTags("</span>");
    //    // 下面这两项,如果你要高亮如文字内容等有很多字的字段,必须配置,不然会导致高亮不全,文章内容缺失等
    //    highlightBuilder.fragmentSize(800000);  // 最大高亮分片数
    //    highlightBuilder.numOfFragments(0);     // 从第一个分片获取高亮片段
    //    return highlightBuilder;
    //}
}
